package cn.iocoder.yudao.module.shop.info.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.business.audit.enums.AuditStatusEnum;
import cn.iocoder.yudao.framework.business.audit.pojo.form.AuditSaveDTO;
import cn.iocoder.yudao.framework.business.basic.dao.query.CoreLambdaQueryWrapper;
import cn.iocoder.yudao.framework.business.basic.pojo.dto.LocationQueryDTO;
import cn.iocoder.yudao.framework.business.basic.pojo.dto.SortQueryDTO;
import cn.iocoder.yudao.framework.business.eligibility.service.EligibilityApplyBaseService;
import cn.iocoder.yudao.framework.business.evaluation.enums.ScoreLevelEnum;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.region.RegionUtils;
import cn.iocoder.yudao.framework.region.bo.RegionNode;
import cn.iocoder.yudao.framework.region.enums.RegionDataSet;
import cn.iocoder.yudao.framework.region.vo.getter.AddressSaveGetter;
import cn.iocoder.yudao.framework.util.TransactionUtils;
import cn.iocoder.yudao.module.api.brokerage.agency.BrokerageAgencyApi;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.api.infrastructure.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.api.infrastructure.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.api.member.agency.AgencyUserApi;
import cn.iocoder.yudao.module.api.member.agency.vo.AgencyUserVO;
import cn.iocoder.yudao.module.api.member.assets.MemberDigitalAssetsApi;
import cn.iocoder.yudao.module.api.member.user.MemberUserApi;
import cn.iocoder.yudao.module.api.product.evaluation.ProductEvaluationApi;
import cn.iocoder.yudao.module.api.product.spu.ProductSpuApi;
import cn.iocoder.yudao.module.api.promotion.groupon.GrouponPackageApi;
import cn.iocoder.yudao.module.api.shop.info.dto.ShopInfoDTO;
import cn.iocoder.yudao.module.api.shop.info.enums.ShopNotifySceneEnum;
import cn.iocoder.yudao.module.api.shop.info.enums.ShopOwnerTypeEnum;
import cn.iocoder.yudao.module.api.shop.info.enums.ShopStatusEnum;
import cn.iocoder.yudao.module.shop.admin.dao.ShopUserMapper;
import cn.iocoder.yudao.module.shop.admin.pojo.po.ShopUser;
import cn.iocoder.yudao.module.shop.admin.service.ShopUserService;
import cn.iocoder.yudao.module.shop.category.pojo.po.ShopCategory;
import cn.iocoder.yudao.module.shop.category.service.ShopCategoryService;
import cn.iocoder.yudao.module.shop.info.dal.dataobject.ShopEnterPackageDO;
import cn.iocoder.yudao.module.shop.info.dal.dataobject.ShopInfoDO;
import cn.iocoder.yudao.module.shop.info.dal.mysql.ShopEnterPackageMapper;
import cn.iocoder.yudao.module.shop.info.dal.mysql.ShopInfoMapper;
import cn.iocoder.yudao.module.shop.info.mq.ShopInfoProducer;
import cn.iocoder.yudao.module.shop.info.vo.*;
import com.github.yulichang.toolkit.JoinWrappers;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_MEMBER;
import static cn.iocoder.yudao.module.api.shop.ErrorCodeConstants.*;

/**
 * 店铺信息 Service 实现类
 *
 * @author 山野羡民（1032694760@qq.com）
 */
@Service
@Validated
@Slf4j
public class ShopInfoServiceImpl extends EligibilityApplyBaseService<ShopInfoDO, AdminShopInfoVO, AppShopInfoSaveDTO, ShopInfoQueryDTO> implements ShopInfoService {

    @Resource
    private ShopInfoMapper shopInfoMapper;
    @Resource
    private ShopUserMapper shopUserMapper;
    @Resource
    private ShopEnterPackageMapper shopEnterPackageMapper;

    @Resource
    private ShopInfoProducer shopInfoProducer;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopCategoryService shopCategoryService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopUserService shopUserService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private MemberDigitalAssetsApi memberDigitalAssetsApi;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private MemberUserApi memberUserApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductEvaluationApi productEvaluationApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductSpuApi productSpuApi;
    @Resource
    private GrouponPackageApi grouponPackageApi;
    @Resource
    private NotifyMessageSendApi notifyMessageSendApi;
    @Resource
    private ConfigApiV2 configApiV2;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private AgencyUserApi agencyUserApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private BrokerageAgencyApi brokerageAgencyApi;

    @Override
    public AdminShopInfoVO convertToViewObject(@Nonnull ShopInfoDO entity) {
        return BeanUtils.toBean(entity, AdminShopInfoVO.class);
    }

    @DataPermission(enable = false)
    @Override
    public Long getOwnerMemberUserId(Long id) {
        CoreLambdaQueryWrapper<ShopInfoDO> wrapper = new CoreLambdaQueryWrapper<>();
        wrapper.select(ShopInfoDO::getUserId);
        wrapper.eq(ShopInfoDO::getId, id);
        wrapper.limitN(1);
        ShopInfoDO shopInfoDO = shopInfoMapper.selectOne(wrapper);
        if (shopInfoDO == null) {
            return 0L;
        }
        return shopInfoDO.getUserId();
    }

    @DataPermission(enable = false)
    @Override
    public Long getShopIdByMemberUserId(Long memberUserId) {
        return shopInfoMapper.selectIdByMemberUserId(memberUserId);
    }

    @DataPermission(enable = false)
    @Override
    public Long getShopIdByAdminUserId(Long adminUserId) {
        return shopUserMapper.selectShopIdByAdminUserId(adminUserId);
    }

    @DataPermission(enable = false)
    @Override
    public double getServiceFeePercent(Long id) {
        return shopInfoMapper.selectServiceFeePercentById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long createInfo(ShopInfoCreateVO reqVO) {
        // 校验主营类目
        shopCategoryService.validateFirstLevelCategory(reqVO.getCategoryId());
        // 插入店铺基本信息
        ShopInfoDO shopInfoDO = BeanUtils.toBean(reqVO, ShopInfoDO.class);
        shopInfoDO.setUserId(0L);
        autoAssignAddress(shopInfoDO, reqVO);
        shopInfoDO.setStatus(ShopStatusEnum.OPENING.getValue());
        shopInfoDO.setAuditStatus(AuditStatusEnum.PASS.getValue());
        shopInfoDO.setEnterTime(LocalDateTime.now());
        shopInfoDO.setEnabled(CommonStatusEnum.ENABLE.getStatus());
        shopInfoMapper.insert(shopInfoDO);
        afterCreatedCommit(reqVO, shopInfoDO);
        // 返回
        return shopInfoDO.getId();
    }

    @Override
    protected void afterCreatedCommit(AppShopInfoSaveDTO reqVO, ShopInfoDO shopInfoDO) {
        if (reqVO.getAgencyId() != null && reqVO.getAgencyId() > 0) {
            // 绑定商家与招商人员的关系
            AgencyUserVO agencyUserVO = agencyUserApi.getAgencyUser(reqVO.getAgencyId());
            if (agencyUserVO != null && agencyUserVO.getUserId() != null && agencyUserVO.getUserId() > 0) {
                brokerageAgencyApi.bindRelation(shopInfoDO.getId(), agencyUserVO.getUserId());
            } else {
                log.warn("[afterCreatedCommit] 要绑定招商人员({})不存在，商家(shopId={})入驻费忽略绑定", reqVO.getAgencyId(), shopInfoDO.getId());
            }
        } else {
            log.info("[afterCreatedCommit] 未获取到招商人员编号参数，商家(shopId={})入驻费忽略绑定", shopInfoDO.getId());
        }
        TransactionUtils.afterCommit(() -> {
            onAuditAfter(Objects.equals(shopInfoDO.getAuditStatus(), AuditStatusEnum.PASS.getValue()), shopInfoDO);
            return null;
        });
    }

    @Override
    public void validateCanCreate(Long userId, Integer userType, AppShopInfoSaveDTO reqVO) {
        super.validateCanCreate(userId, userType, reqVO);
        shopCategoryService.validateFirstLevelCategory(reqVO.getCategoryId());
    }

    @Nonnull
    @Override
    protected ShopInfoDO buildSaveInfo(AppShopInfoSaveDTO reqVO) {
        ShopInfoDO shopInfoDO = BeanUtils.toBean(reqVO, ShopInfoDO.class);
        shopInfoDO.setEnterPackageId(0L);
        shopInfoDO.setEnterTime(null);
        shopInfoDO.setExpireTime(null);
        if (shopInfoDO.getType() == null || Objects.equals(shopInfoDO.getType(), ShopOwnerTypeEnum.WHOLESALER.getValue())) {
            shopInfoDO.setType(ShopOwnerTypeEnum.STORE_SELLER.getValue());
        }
        shopInfoDO.setStatus(ShopStatusEnum.UNAVAILABLE.getValue());
        return shopInfoDO;
    }

    @Nonnull
    @Override
    protected AdminShopInfoVO buildApplyInfo(@Nonnull ShopInfoDO aDo) {
        RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, aDo.getCountyCode(), aDo);
        AdminShopInfoVO info = BeanUtils.toBean(aDo, AdminShopInfoVO.class);
        ShopCategory categoryDO = shopCategoryService.getCategory(info.getCategoryId());
        info.assignCategory(categoryDO);
        if (aDo.getEnterPackageId() == null || aDo.getEnterPackageId() <= 0) {
            info.setEnterPackageId(0L);
            info.setEnterPackageName("无");
            return info;
        }
        // TODO 获取套餐信息
        return info;
    }

    @Override
    public void validateCanUpdate(Long id, AppShopInfoSaveDTO reqVO) {
        super.validateCanUpdate(id, reqVO);
        shopCategoryService.validateFirstLevelCategory(reqVO.getCategoryId());
    }

    @Override
    public void updateInfo(ShopInfoBasicUpdateReqVO reqVO) {
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        ShopInfoDO updateObj = BeanUtils.toBean(reqVO, ShopInfoDO.class);
        shopInfoMapper.updateById(updateObj);
        shopInfoProducer.sendShopInfoUpdateMessage(BeanUtils.toBean(shopInfoDO, ShopInfoDTO.class));
    }

    @Override
    public void updateInfo(Long userId, Integer userType, ShopInfoBasicUpdateReqVO reqVO) {
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        ShopInfoDO updateObj = BeanUtils.toBean(reqVO, ShopInfoDO.class);
        shopInfoMapper.updateById(updateObj);
        saveShopAdminInfo(shopInfoDO);
        shopInfoProducer.sendShopInfoUpdateMessage(BeanUtils.toBean(shopInfoDO, ShopInfoDTO.class));
    }

    @Override
    public void updateCategory(ShopCategoryUpdateReqVO reqVO) {
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        // 校验主营类目
        ShopCategory categoryDO = shopCategoryService.validateFirstLevelCategory(reqVO.getCategoryId());
        // 更新
        shopInfoDO.setCategoryId(categoryDO.getId());
        shopInfoMapper.updateById(shopInfoDO);
    }

    @Override
    public void updateCategory(Long userId, Integer userType, ShopCategoryUpdateReqVO reqVO) {
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(userId, userType, reqVO.getId());
        // 校验主营类目
        ShopCategory categoryDO = shopCategoryService.validateFirstLevelCategory(reqVO.getCategoryId());
        // 更新
        shopInfoDO.setCategoryId(categoryDO.getId());
        shopInfoMapper.updateById(shopInfoDO);
    }

    @Override
    public void updateStatus(ShopStatusUpdateReqVO reqVO) {
        if (Objects.equals(reqVO.getStatus(), ShopStatusEnum.UNAVAILABLE.getValue())) {
            throw exception(SHOP_INFO_STATUS_DENIED, ShopStatusEnum.UNAVAILABLE.getDesc());
        }
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        if (!Objects.equals(shopInfoDO.getAuditStatus(), AuditStatusEnum.PASS.getValue())) {
            throw exception(SHOP_INFO_EXISTS_UNAVAILABLE);
        }
        // 更新
        shopInfoDO.setStatus(reqVO.getStatus());
        shopInfoMapper.updateById(shopInfoDO);
    }

    @Override
    public void updateStatus(Long userId, Integer userType, ShopStatusUpdateReqVO reqVO) {
        if (Objects.equals(reqVO.getStatus(), ShopStatusEnum.UNAVAILABLE.getValue())) {
            throw exception(SHOP_INFO_STATUS_DENIED, ShopStatusEnum.UNAVAILABLE.getDesc());
        }
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(userId, userType, reqVO.getId());
        if (!Objects.equals(shopInfoDO.getAuditStatus(), AuditStatusEnum.PASS.getValue())) {
            throw exception(SHOP_INFO_EXISTS_UNAVAILABLE);
        }
        // 更新
        shopInfoDO.setStatus(reqVO.getStatus());
        shopInfoMapper.updateById(shopInfoDO);
    }

    @Override
    public void updateEnabled(ShopEnabledSaveVO reqVO) {
        // 校验存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        shopInfoDO.setEnabled(Boolean.TRUE.equals(reqVO.getEnabled()) ? CommonStatusEnum.ENABLE.getStatus() : CommonStatusEnum.DISABLE.getStatus());
        // 更新
        shopInfoMapper.updateById(shopInfoDO);
    }

    @Override
    protected void buildAuditInfo(boolean passed, @Nonnull ShopInfoDO aDo, @Nonnull AuditSaveDTO reqVO) {
        super.buildAuditInfo(passed, aDo, reqVO);
        if (passed) {
            aDo.setStatus(ShopStatusEnum.OPENING.getValue());
            aDo.setEnterTime(LocalDateTime.now());
        } else {
            aDo.setStatus(ShopStatusEnum.UNAVAILABLE.getValue());
        }
    }

    @Override
    protected void onAuditAfter(boolean passed, @Nonnull ShopInfoDO aDo) {
        if (passed) {
            TransactionUtils.transactional(() -> {
                doPassed(aDo);
                return null;
            });
        }
        // 审核通过或不通过，发送站内信
        Map<String, Object> templateParams = MapUtil.<String, Object>builder()
                .put("name", aDo.getName())
                .put("contactName", aDo.getContactName())
                .put("contactMobile", aDo.getContactMobile())
                .put("auditTime", aDo.getAuditTime())
                .put("auditNote", passed ? "" : aDo.getAuditNote())
                .build();
        NotifySendSingleToUserReqDTO dto = new NotifySendSingleToUserReqDTO();
        dto.setUserId(aDo.getUserId());
        if (passed) {
            dto.setTemplateCode(ShopNotifySceneEnum.APPLY_AUDIT_APPROVE.getCode());
        } else {
            dto.setTemplateCode(ShopNotifySceneEnum.APPLY_AUDIT_REJECT.getCode());
        }
        dto.setTemplateParams(templateParams);
        try {
            notifyMessageSendApi.sendSingleMessageToMember(dto);
        } catch (Exception e) {
            log.error("[shop][onAuditAfter][vo({}) 给对应的用户发送站内信失败]", JsonUtils.toJsonString(dto), e);
        }
    }

    private void doPassed(@Nonnull ShopInfoDO aDo) {
        saveShopAdminInfo(aDo);
        if (aDo.getUserId() != null && aDo.getUserId() > 0) {
            MemberConfigRespDTO config = configApiV2.getConfig(CONFIG_MEMBER, MemberConfigRespDTO.class);
            int cryptocurrency = config.getGiveCryptocurrencyFreezing();
            memberDigitalAssetsApi.changeCryptocurrencyFreezing(aDo.getUserId(), 1d * cryptocurrency);
            log.debug("店铺资料（{}）审核通过，赠送冻结中的联盟豆 {} 个", aDo.getName(), cryptocurrency);
        } else {
            log.warn("[doPassed] 店铺资料（{}）审核通过，未绑定前台用户，忽略冻结中的联盟豆赠送", aDo.getName());
        }
        // 店铺资料审核通过，发布店铺审核通过消息以便其他模块监听到后做相关操作
        shopInfoProducer.sendShopAuditPassMessage(BeanUtils.toBean(aDo, ShopInfoDTO.class));
    }

    private void saveShopAdminInfo(ShopInfoDO shopInfoDO) {
        // 优先使用前台用户绑定的手机号码作为后台用户的登录账号
        String mobile = "";
        if (shopInfoDO.getUserId() != null && shopInfoDO.getUserId() > 0) {
            mobile = memberUserApi.getUserMobile(shopInfoDO.getUserId());
        }
        if (StrUtil.isBlank(mobile)) {
            mobile = shopInfoDO.getContactMobile();
        }
        if (StrUtil.isBlank(mobile)) {
            log.warn("[shop][saveShopAdminInfo][手机号码为空，忽略商家({})的管理员账号创建]", shopInfoDO.getName());
            return;
        }
        // 店铺资料审核通过，则生成店铺的后台管理员账号
        shopUserService.createAdminIfNecessary(shopInfoDO.getId(), shopInfoDO.getUserId(),
                shopInfoDO.getContactName(), mobile);
    }

    @Override
    public void updateEnterPackage(ShopEnterPackageUpdateReqVO reqVO) {
        // 校验套餐存在
        ShopEnterPackageDO enterPackageDO = shopEnterPackageMapper.selectById(reqVO.getEnterPackageId());
        if (null == enterPackageDO) {
            throw exception(ENTER_PACKAGE_NOT_EXISTS);
        }
        // 校验店铺信息存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        if (!Objects.equals(shopInfoDO.getAuditStatus(), AuditStatusEnum.PASS.getValue())) {
            throw exception(ENTER_PACKAGE_FREE_DINED);
        }
        // 更新
        shopInfoDO.setEnterPackageId(enterPackageDO.getId());
        if (null == shopInfoDO.getEnterTime()) {
            shopInfoDO.setEnterTime(LocalDateTime.now());
        }
        if (null == shopInfoDO.getExpireTime()) {
            shopInfoDO.setExpireTime(LocalDateTime.now().plusYears(enterPackageDO.getYear()));
        } else {
            shopInfoDO.setExpireTime(shopInfoDO.getExpireTime().plusYears(enterPackageDO.getYear()));
        }
        // 套餐购买或赠送成功，则更新为“营业中”状态
        shopInfoDO.setStatus(ShopStatusEnum.OPENING.getValue());
        shopInfoDO.setEnabled(CommonStatusEnum.ENABLE.getStatus());
        shopInfoMapper.updateById(shopInfoDO);
        // TODO 考虑加入套餐信息的更新日志
    }

    @Override
    public void updateServiceFee(ShopServiceFeeUpdateReqVO reqVO) {
        // 校验店铺信息存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        // 更新
        ShopInfoDO updateObj = new ShopInfoDO();
        updateObj.setId(shopInfoDO.getId());
        updateObj.setServiceFeePercent(reqVO.getServiceFeePercent());
        shopInfoMapper.updateById(updateObj);
    }

    @Override
    public void updateDiscountPercent(ShopDiscountPercentUpdateReqVO reqVO) {
        // 校验店铺信息存在
        ShopInfoDO shopInfoDO = validateExists(reqVO.getId());
        // 更新
        ShopInfoDO updateObj = new ShopInfoDO();
        updateObj.setId(shopInfoDO.getId());
        updateObj.setDiscountPercent(reqVO.getDiscountPercent());
        shopInfoMapper.updateById(updateObj);
    }

    @Override
    public void deleteInfo(Long id) {
        throw exception(FORBIDDEN);
    }

    @DataPermission(enable = false)
    @Nullable
    @Override
    public ShopInfoDO getInfo(Long id) {
        ShopInfoDO shopInfoDO = shopInfoMapper.selectById(id);
        if (shopInfoDO != null) {
            RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO);
        }
        return shopInfoDO;
    }

    @DataPermission(enable = false)
    @Nullable
    @Override
    public ShopInfoDO getDetailByUserId(Long userId, Integer userType) {
        ShopInfoDO entity;
        if (!Objects.equals(userType, UserTypeEnum.MEMBER.getValue())) {
            ShopUser shopUserDO = shopUserMapper.selectOne(userId, userType, null);
            if (shopUserDO == null) {
                return null;
            }
            entity = shopInfoMapper.selectById(shopUserDO.getShopId());
        } else {
            entity = shopInfoMapper.selectByUserIdAndUserType(userId, null);
        }
        if (entity != null) {
            RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, entity.getCountyCode(), entity);
        }
        return entity;
    }

    @Override
    public PageResult<ShopInfoDO> getInfoPage(Integer current, Integer size, ShopInfoQueryDTO dto) {
        if (StrUtil.isNotBlank(dto.getCategoryCode())) {
            ShopCategory categoryDO = shopCategoryService.getCategory(dto.getCategoryCode(), CommonStatusEnum.ENABLE.getStatus());
            if (categoryDO != null && (dto.getCategoryId() == null || dto.getCategoryId() <= 0)) {
                dto.setCategoryId(categoryDO.getId());
            }
        }
        PageResult<ShopInfoDO> pageResult = shopInfoMapper.selectPage(current, size, dto);
        return toPageResult(pageResult);
    }

    @DataPermission(enable = false)
    @Override
    public List<ShopInfoDO> getInfoList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        List<ShopInfoDO> list = shopInfoMapper.selectByIds(ids);
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        list.forEach(shopInfoDO -> RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO));
        return list;
    }

    @DataPermission(enable = false)
    @Override
    public List<ShopInfoDO> getInfoList(Long userId, Integer userType) {
        List<ShopInfoDO> list = shopInfoMapper.selectJoinList(ShopInfoDO.class, JoinWrappers.lambda(ShopInfoDO.class)
                .selectAll(ShopInfoDO.class)
                .leftJoin(ShopUser.class, ShopUser::getShopId, ShopInfoDO::getId)
                .eq(Objects.equals(userType, UserTypeEnum.MEMBER.getValue())
                        ? ShopUser::getMemberUserId
                        : ShopUser::getAdminUserId, userId));
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        list.forEach(shopInfoDO -> RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO));
        return list;
    }

    @Override
    public PageResult<ShopInfoDO> getInfoPage(Integer current, Integer size, AppShopInfoQueryDTO reqVO) {
        LocationQueryDTO location = new LocationQueryDTO();
        location.setLongitude(reqVO.getLongitude());
        location.setLatitude(reqVO.getLatitude());
        SortQueryDTO sort = new SortQueryDTO();
        sort.setSortType(reqVO.getSortType());
        sort.setSortAsc(reqVO.getSortAsc());
        if (Boolean.TRUE.equals(reqVO.getGroupon())) {
            // 有团购套餐的店铺
            Map<Long, Integer> shopDistanceMap = grouponPackageApi.getShopDistanceMap(location, sort);
            return toPageResult(current, size, shopDistanceMap, reqVO.getCityCode());
        }
        if (reqVO.getCategoryId() != null && reqVO.getCategoryId() > 0) {
            // 指定行业类目的店铺，由于店铺只关联了顶级的行业类目，需要结合店铺产品关联的行业类目来查询
            Set<Long> mergeCategoryIds = new HashSet<>();
            mergeCategoryIds.add(reqVO.getCategoryId());
            Set<Long> tmpCategoryIds = shopCategoryService.getDescendentCategoryIdsFromCache(reqVO.getCategoryId(), CommonStatusEnum.ENABLE.getStatus());
            if (CollUtil.isNotEmpty(tmpCategoryIds)) {
                mergeCategoryIds.addAll(tmpCategoryIds);
            }
            // TODO 产品量及店铺很多时，这里应该会存在性能问题，需改用店铺信息表和产品表联合查询
            Map<Long, Integer> shopDistanceMap = productSpuApi.getShopDistanceMapByPlatformCategoryIds(mergeCategoryIds, location, sort);
            return toPageResult(current, size, shopDistanceMap, reqVO.getCityCode());
        }
        Set<Long> categoryIds = new HashSet<>();
        if (Boolean.TRUE.equals(reqVO.getO2o())) {
            // 生活商圈的店铺
            Set<Long> tmpCategoryIds = shopCategoryService.getIdsByO2O();
            if (CollUtil.isNotEmpty(tmpCategoryIds)) {
                categoryIds.addAll(tmpCategoryIds);
            }
        }
        if (StrUtil.isNotBlank(reqVO.getCategoryCode())) {
            // 指定主营类目的店铺
            ShopCategory categoryDO = shopCategoryService.getCategory(reqVO.getCategoryCode(), CommonStatusEnum.ENABLE.getStatus());
            if (categoryDO != null) {
                categoryIds.add(categoryDO.getId());
            }
        }
        reqVO.setCategoryIds(categoryIds);
        PageResult<ShopInfoDO> pageResult = shopInfoMapper.selectPage(current, size, reqVO);
        return toPageResult(pageResult);
    }

    @DataPermission(enable = false) // 禁用数据权限，避免选择合作商家时查询不到数据
    @Override
    public List<ShopInfoDO> searchInfoList(String keyword) {
        return shopInfoMapper.selectListByKeyword(keyword);
    }

    @DataPermission(enable = false) // 禁用数据权限，避免选择合作商家时查询不到数据
    @Override
    public List<ShopInfoDO> pickInfoList(Collection<Long> ids) {
        return shopInfoMapper.selectByIds(ids);
    }

    private PageResult<ShopInfoDO> toPageResult(int current, int size, Map<Long, Integer> shopDistanceMap, String cityCode) {
        if (CollUtil.isEmpty(shopDistanceMap)) {
            return PageResult.empty();
        }
        Set<Long> shopIds = new HashSet<>(shopDistanceMap.keySet());
        List<ShopInfoDO> list = shopInfoMapper.selectStoreSellerByIds(shopIds, cityCode);
        if (CollUtil.isEmpty(list)) {
            return PageResult.empty();
        }
        size = Math.max(size, current);
        int start = Math.max(current - 1, 0) * size;
        int end = Math.min(start + size, list.size());
        list.forEach(shopInfoDO -> {
            RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO);
            Integer distanceKm = shopDistanceMap.get(shopInfoDO.getId());
            double distance = distanceKm == null || distanceKm < 0 ? -1D : distanceKm / 1000D;
            shopInfoDO.setDistance(distance);
        });
        PageResult<ShopInfoDO> pageResult = new PageResult<>(list.subList(start, end), (long) list.size());
        return toPageResult(pageResult);
    }

    @Nonnull
    private PageResult<ShopInfoDO> toPageResult(PageResult<ShopInfoDO> pageResult) {
        List<ShopInfoDO> list = pageResult.getList();
        if (CollUtil.isEmpty(list)) {
            return PageResult.empty();
        }
        Map<Long, Float> scoreMap = productEvaluationApi.batchCalculateScoreByShopIds(convertSet(list, ShopInfoDO::getId));
        list.forEach(shopInfoDO -> {
            RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO);
            shopInfoDO.setScore(scoreMap.getOrDefault(shopInfoDO.getId(), 5F));
        });
        return pageResult;
    }

    @DataPermission(enable = false)
    @Override
    public List<ShopInfoDO> getInfoList(Integer status) {
        List<ShopInfoDO> list = shopInfoMapper.selectListByStatus(status);
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        list.forEach(shopInfoDO -> RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO));
        return list;
    }

    @DataPermission(enable = false)
    @Override
    public List<ShopInfoDO> getInfoList(Collection<Long> ids, Integer status) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        ids.removeIf(id -> id == null || id <= 0);
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<ShopInfoDO> list = shopInfoMapper.selectListByIdsAndStatus(ids, status);
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        list.forEach(shopInfoDO -> RegionUtils.autoAssignCounty(RegionDataSet.ONLY_LATEST, shopInfoDO.getCountyCode(), shopInfoDO));
        return list;
    }

    @DataPermission(enable = false)
    @Override
    public ShopCategory getShopCategory(Long shopId) {
        ShopInfoDO shopInfoDO = shopInfoMapper.selectOne(ShopInfoDO::getId, shopId);
        if (shopInfoDO == null) {
            return null;
        }
        return shopCategoryService.getCategory(shopInfoDO.getCategoryId());
    }

    @Override
    public Set<Long> getShopIdsByDisabled() {
        return shopInfoMapper.selectIdsByDisabled();
    }

    @Override
    public Set<Long> getShopIdsByO2O() {
        // TODO 后续冗余存储主营类目编码
        Set<Long> categoryIds = shopCategoryService.getIdsByO2O();
        return shopInfoMapper.selectShopIdsByCategoryIds(categoryIds);
    }

    @DataPermission(enable = false)
    @Override
    public ShopInfoDO validateExists(Long id) {
        ShopInfoDO aDo = shopInfoMapper.selectById(id);
        Assertions.notNull(aDo, "店铺不存在");
        return aDo;
    }

    @DataPermission(enable = false)
    @Override
    public ShopInfoDO validateExists(Long id, Integer userType, Long userId) {
        ShopUser shopUserDO = shopUserMapper.selectOne(userId, userType, id);
        Assertions.notNull(shopUserDO, "还未开通店铺或没有该店铺的操作权限");
        ShopInfoDO aDo = shopInfoMapper.selectById(shopUserDO.getShopId());
        Assertions.notNull(aDo, "店铺不存在");
        return aDo;
    }

    @Override
    protected ShopInfoMapper getInjectedMapper() {
        return shopInfoMapper;
    }

    @Override
    public ShopInfoDO updateAddress(AddressSaveGetter reqVO) {
        // 校验存在
        ShopInfoDO aDo = validateExists(reqVO.getId());
        autoAssignAddress(aDo, reqVO);
        // 更新
        getInjectedMapper().updateById(aDo);
        shopInfoProducer.sendShopInfoUpdateMessage(BeanUtils.toBean(aDo, ShopInfoDTO.class));
        return aDo;
    }

    @Override
    public ShopInfoDO updateAddress(Long userId, Integer userType, AddressSaveGetter reqVO) {
        // 校验存在
        ShopInfoDO aDo = validateExists(reqVO.getId(), userType, userId);
        autoAssignAddress(aDo, reqVO);
        // 更新
        getInjectedMapper().updateById(aDo);
        return aDo;
    }


    @Override
    public void updateScoreWhenEvaluation(Long id) {
        ShopInfoDO shopInfoDO = shopInfoMapper.selectById(id);
        if (shopInfoDO == null) {
            return;
        }
        float scores = productEvaluationApi.calculateScoreByShopId(id);
        shopInfoDO.setScore(scores);
        shopInfoDO.setScoreLevel(ScoreLevelEnum.fromScore(scores).getValue());
        shopInfoMapper.updateById(shopInfoDO);
    }

    @DataPermission(enable = false)
    @Override
    public Set<Long> getShopIdsByCategoryCode(String categoryCode) {
        if (StrUtil.isEmpty(categoryCode)) {
            return CollUtil.newHashSet();
        }
        ShopCategory categoryDO = shopCategoryService.getCategory(categoryCode, CommonStatusEnum.ENABLE.getStatus());
        if (categoryDO == null) {
            return CollUtil.newHashSet();
        }
        List<ShopInfoDO> shops = shopInfoMapper.selectList(new CoreLambdaQueryWrapper<ShopInfoDO>()
                .eq(ShopInfoDO::getCategoryId, categoryDO.getId()));
        if (CollUtil.isEmpty(shops)) {
            return CollUtil.newHashSet();
        }
        return shops.stream().map(ShopInfoDO::getId).collect(Collectors.toSet());
    }

    @DataPermission(enable = false)
    @Override
    public Set<Long> getShopIdsByCategoryId(Long categoryId) {
        if (categoryId == null || categoryId <= 0) {
            return CollUtil.newHashSet();
        }
        List<ShopInfoDO> shops = shopInfoMapper.selectList(new CoreLambdaQueryWrapper<ShopInfoDO>()
                .eq(ShopInfoDO::getCategoryId, categoryId));
        if (CollUtil.isEmpty(shops)) {
            return CollUtil.newHashSet();
        }
        return shops.stream().map(ShopInfoDO::getId).collect(Collectors.toSet());
    }

    @DataPermission(enable = false)
    @Override
    public Set<Long> getShopIdsByCountyCodes(Set<String> countyCodes) {
        if (CollUtil.isEmpty(countyCodes)) {
            return CollUtil.newHashSet();
        }
        List<ShopInfoDO> shops = shopInfoMapper.selectList(new CoreLambdaQueryWrapper<ShopInfoDO>()
                .in(ShopInfoDO::getCountyCode, countyCodes));
        if (CollUtil.isEmpty(shops)) {
            return CollUtil.newHashSet();
        }
        return shops.stream().map(ShopInfoDO::getId).collect(Collectors.toSet());
    }

    @DataPermission(enable = false)
    @Override
    public Set<Long> getShopIdsByCityCodes(Set<String> cityCodes) {
        if (CollUtil.isEmpty(cityCodes)) {
            return CollUtil.newHashSet();
        }
        // TODO 后续冗余存储省份及城市编码
        List<String> countyCodes = new ArrayList<>();
        List<RegionNode> regionNodes = RegionUtils.getRegionList(RegionDataSet.ONLY_LATEST);
        for (RegionNode regionNode : regionNodes) {
            if (CollUtil.contains(cityCodes, regionNode.getParentCode())) {
                countyCodes.add(regionNode.getCode());
            }
        }
        if (CollUtil.isEmpty(countyCodes)) {
            countyCodes = cityCodes.stream().toList();// 兼容前端错传为区县编码的情况
        }
        List<Long> shopIds = shopInfoMapper.selectIdsByCountyCodes(countyCodes);
        if (CollUtil.isEmpty(shopIds)) {
            return CollUtil.newHashSet();
        }
        return new HashSet<>(shopIds);
    }

    @DataPermission(enable = false)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteInfoByMemberUserId(Long memberUserId) {
        shopUserMapper.delete(new CoreLambdaQueryWrapper<ShopUser>()
                .eq(ShopUser::getMemberUserId, memberUserId));
        List<ShopUser> shopUserDOS = shopUserMapper.selectList(ShopUser::getMemberUserId, memberUserId);
        Set<Long> shopIds = shopUserDOS.stream().map(ShopUser::getShopId).collect(Collectors.toSet());
        shopInfoMapper.delete(new CoreLambdaQueryWrapper<ShopInfoDO>().inIfPresent(ShopInfoDO::getId, shopIds));
        // TODO 删除更多商家关联信息
    }

    @DataPermission(enable = false)
    @Override
    public ShopInfoDO getShopInfoByMemberUserId(Long memberUserId) {
        return shopInfoMapper.selectOne(ShopInfoDO::getUserId, memberUserId);
    }

    @DataPermission(enable = false)
    @Override
    public ShopInfoDO getShopInfoByAdminUserId(Long adminUserId) {
        ShopUser shopUserDO = shopUserMapper.selectOne(adminUserId, UserTypeEnum.ADMIN.getValue(), null);
        if (shopUserDO == null) {
            return null;
        }
        return shopInfoMapper.selectOne(ShopInfoDO::getId, shopUserDO.getShopId());
    }

}
